It has become commonplace, of late, for software developers to employ programming tools when developing program code. Such tools may include environments for efficiently writing (an editor), compiling (a compiler), executing (a runtime) and debugging the execution of (a debugger) program code. It is well accepted that program may be referred to in terms of a program “flow” that is representative of components of a whole that is a program, or a part thereof. The components are typically lines of code set apart from other lines of code based on the purposed served. The components may be seen as “nodes” in a flow map, which may also be called a flow graph or flow chart. Between the nodes may be “connections” that indicate the nodes that follow other nodes in the flow.
Debugging code often requires that breakpoints be placed in the code by the developer so that, when the code is compiled and executed, the execution pauses at the breakpoint. While execution of the code is paused, the developer may, for instance, review the values of certain variables or determine that a lock has been obtained on given object. Once the developer has reviewed the available information for the breakpoint, a command may be given so that execution may continue, at least until execution pauses at the next breakpoint.
When lines of program code prepared by a software developer are executed in a runtime, it may be that the code has been optimized (by the compiler, alternatively called “deploy tooling”) so that, when executed, the program runs more efficiently than would have been the case if executed precisely as written. Compilers that optimize code may scan through the code and use a set of use logical rules to determine where efficiencies may be found through the elimination of various instructions and, often, the introduction of some new instructions to compensate for some of the eliminated instructions.
When a flow is optimized, nodes and connections in that flow may be repositioned and reorganized such that the flow is more efficient. As a result, it may be considered that there are two types of flow: a user defined flow, which is developed in the tooling (the writing environment); and an optimized flow, which is used in the runtime (the execution environment). Because of this rearrangement, a flow debugger cannot always identify a connection in the optimized flow that corresponds to a given connection in the user defined flow. Consequently, where the user (software developer) has placed a breakpoint on the given connection between nodes in the user defined flow, this breakpoint may not map well to a particular connection in the optimized flow. As such, it may not be clear to the debugger precisely where to place corresponding breakpoint in the optimized flow, i.e., where to pause execution.
One solution to this problem is to avoid optimization while debugging. That is, rather than producing an optimized flow, the user defined flow is compiled to produces a special “debuggable” version of the user defined flow for execution. The developer may then use the results of the execution of the debuggable version of the flow to find errors and inconsistencies. However, by doing so, the developer is not debugging a “true” (optimized) version of the executable code that will be run in a finished product.